
namespace Patterns4Net.ArchitectureExplorer.Diagram.Services.Relations
{
    using System.Linq;
    using Mono.Cecil;
    using Mono.Cecil.Rocks;
    using ArchitectureExplorer.Diagram.Relations;
    using Core.CecilExtensions;
    using Attributes.Relations;

    /// <summary>
    /// Adds relations of type <see cref="Uses"/> into the diagram.
    /// </summary>
    [DiagramUpdaterExport(Order = 50)]
    public class UsesProvider : RelationsProvider
    {
        protected override void Visit(TypeInfo typeInfo, TypeDefinition typeDefinition)
        {
            // fields decorated with [Uses] attribute
            foreach (var field in typeDefinition.Fields)
            {
                if (field.HasAttribute<UsesAttribute>())
                {
                    this.AddRelationIfExists(typeInfo, field.FieldType);
                }
            }

            var staticMethodsCalss = typeDefinition.Methods
                .SelectMany(x => x.GetMethodCalls())
                .Where(ctorCall => ctorCall.Method.IsStatic());
            foreach (var methodCall in staticMethodsCalss)
            {
                this.AddRelationIfCompositionOrAggregationNotExists(typeInfo, methodCall.Method.DeclaringType);
            }

            foreach (var ctorCall in typeDefinition.Methods.SelectMany(x => x.GetConstructorCalls()))
            {
                this.AddRelationIfCompositionOrAggregationNotExists(typeInfo, ctorCall.Type);
            }

            foreach (var method in typeDefinition.GetMethods())
            {
                if (method.OverridesBaseMethodOrImplementsInterface() ||
                    method.IsGetter || method.IsSetter)
                {
                    continue;
                }

                this.AddRelationIfCompositionOrAggregationNotExists(typeInfo, method.ReturnType);
                foreach (var parameter in method.Parameters)
                {
                    this.AddRelationIfCompositionOrAggregationNotExists(typeInfo, parameter.ParameterType);
                }
            }
        }

        protected override Relation CreateRelation(DiagramElement source, DiagramElement target, Cardinality cardinality)
        {
            return Uses.CreateUnidirectional(source, target);
        }

        protected override bool IsMadeObsoleteBy(Relation relation)
        {
            return relation.GetType() == typeof(Uses);
        }

        private Relation AddRelationIfCompositionOrAggregationNotExists(TypeInfo source, TypeReference target)
        {
            bool otherFound = this.Diagram.Relations.Any(r =>
                                       (r.GetType() == typeof (Aggregation) ||
                                        r.GetType() == typeof (Composition) ||
                                        r.GetType() == typeof (Association)) &&
                                       (r.Source == source &&
                                        r.Target.GetTag<TypeDefinition>() != null &&
                                        r.Target.GetTag<TypeDefinition>().IsEqual(target)));
            if (otherFound == false)
            {
                return this.AddRelationIfExists(source, target);
            }

            return null;
        }
    }
}
